import {
  DependenciesDef,
  dependenciesMerge,
  installDeps,
  ProjectCreator,
  ProjectCreatorBasicOptions,
  ProjectCreatorBasicState,
  ProjectStructure,
} from 'create-ts-next';
import { isAbsolute, join } from 'path';
import { terminal } from 'terminal-kit';
import { CloudBaseConfig, PackageWithCloudBase } from './types';
import { filterWorkingDir, validateProjectName } from './utils';

/////////////////////////////////////////////////////////////////////////////////
// Dependencies
/////////////////////////////////////////////////////////////////////////////////
const Dependencies: Record<string, DependenciesDef> = {
  typescript: {
    devDependencies: {
      // node env required in typescript
      '@types/node': '^16',
      // typescript
      typescript: '^4.9.5',
      // ts-node, sometimes we want to run the .ts file directly to try anything
      'ts-node': '^10',
    },
  },
  // swc runtime
  swc: {
    devDependencies: {
      '@swc/cli': '^0.1.59',
      '@swc/core': '^1.3.26',
      // 因为部署的实际上仍然是 node.js 环境，所以实际上不需要生成 regenerator-runtime
      // 'regenerator-runtime': '^0.13.11',
    },
  },
  // code stylelint, we just need typescript
  eslint: {
    devDependencies: {
      eslint: '^8',
      '@typescript-eslint/parser': '^5',
      '@typescript-eslint/eslint-plugin': '^5',
    },
  },
  // code formatter
  prettier: {
    devDependencies: {
      prettier: '^2',
      '@types/prettier': '^2',
    },
  },
  // unit test
  mocha: {
    devDependencies: {
      mocha: '^10',
      '@types/mocha': '^10',
      chai: '^4',
      '@types/chai': '^4',
    },
  },
};

/////////////////////////////////////////////////////////////////////////////////
// TcbCreator
/////////////////////////////////////////////////////////////////////////////////
interface TcbCreatorOptions extends ProjectCreatorBasicOptions {
  dir?: string;
  envId?: string;
  region?: string;
}

interface TcbCreatorState extends ProjectCreatorBasicState {
  //
}

export class TcbCreator extends ProjectCreator<TcbCreatorOptions, TcbCreatorState> {
  constructor(opts: TcbCreatorOptions) {
    super(opts, {});
    validateProjectName(this.options.name);
  }

  async startUp(): Promise<this> {
    await this.detectPackageCmd();
    const { name } = this.options;
    terminal(`Create TCB project `).cyan(name);
    process.stdout.write('\n');

    if (this.packageCmd != null) {
      terminal('Packages manager used: ').cyan(this.packageCmd);
      process.stdout.write('\n');
    }

    process.stdout.write('\n');
    this.create(this.getStructure());
    process.stdout.write('\n');

    terminal('Project ').cyan(name);
    terminal(' created. ');

    let installed = false;
    if (this.options.install) {
      if (this.packageCmd != null) {
        terminal('Install dependencies by ').cyan(this.packageCmd);
        terminal('... \n');
        try {
          await installDeps(this.projectRoot, this.packageCmd);
          installed = true;
        } catch (e) {
          //
        }
      }

      process.stdout.write('\n');

      if (this.packageCmd != null && installed) {
        terminal.green('All dependencies has been installed!\n\n');
      }
    }

    // entry the dir
    terminal('You can try the follow commands:\n');
    terminal.blue(`cd ${name}`);
    process.stdout.write('\n');

    const cmdRun =
      this.options.packageManager === 'pnpm' ? this.options.packageManager : `${this.options.packageManager} run`;

    if (!installed) {
      terminal.blue(`${this.options.packageManager} install`);
      process.stdout.write('\n');
    }

    terminal.blue(`${cmdRun} dev`);

    process.stdout.write('\n');
    process.stdout.write('\n');
    return this;
  }

  getStructure(): ProjectStructure {
    return {
      name: '',
      type: 'dir',
      children: [
        // dirs
        {
          name: 'functions',
          type: 'dir',
        },
        // files
        { name: 'package.json', data: this.generatePackageData() },
        { name: '.eslintrc.js' },
        { name: '.gitignore' },
        { name: '.mocharc.json' },
        { name: '.prettierrc' },
        { name: '.swcrc' },
        { name: 'cloudbaserc.json', data: this.generateCloudBaseData() },
        { name: 'pnpm-workspace.yaml' },
        { name: 'README.md' },
        { name: 'tsconfig.json' },
      ],
    };
  }

  generatePackageData(): string {
    const pkgData: PackageWithCloudBase = {
      name: this.options.name,
      version: '1.0.0',
      description: `tcb project ${this.options.name}`,
      scripts: {
        dev: 'swc ./functions --out-dir ./functions -w',
        build: 'swc ./functions --out-dir ./functions',
        aggr: 'npx tcb-toolkit-cli aggr',
      },
      author: '',
      license: 'UNLICENSED',
      private: true,
      ...(this.getDeps() || {}),
      cloudbaseConfig: {
        envId: this.options.envId ?? 'envId',
        region: this.options.region,
        functionRoot: './functions',
      },
      scf: {
        timeout: 5,
        memorySize: 128,
        runtime: 'Nodejs16.13',
        installDependency: true,
        ignore: ['node_modules', 'node_modules/**/*', '*.md', '*.ts', '*.yaml'],
      },
    };
    return JSON.stringify(pkgData, null, 2);
  }

  generateCloudBaseData(): string {
    const config: CloudBaseConfig = {
      envId: this.options.envId ?? 'envId',
      region: this.options.region,
      functionRoot: './functions',
      functions: [],
    };
    return JSON.stringify(config, null, 2);
  }

  getDeps(): DependenciesDef | undefined {
    let deps: DependenciesDef | undefined = undefined;
    Object.values(Dependencies).forEach(lib => {
      deps = dependenciesMerge(deps, lib);
    });
    return deps;
  }

  get projectRoot(): string {
    return isAbsolute(this.options.name)
      ? this.options.name
      : join(...([this.basePath, filterWorkingDir(this.options.dir), this.options.name].filter(Boolean) as string[]));
  }

  getTemplatePath(relative: string): string {
    return join(__dirname, `../template/tcb/${relative}.ejs`);
  }
}
